use super::*;

/// Represents a JavaScript TypedArray object
#[derive(Clone, Copy)]
pub struct TypedArray<'val> {
	pub(crate) env: &'val Env,
	pub(crate) inner: sys::napi_value,
}

impl<'val> TypedArray<'val> {
	/// Creates a new TypedArray from an ArrayBuffer
	pub fn new<'env>(
		env: &'env Env,
		arraybuffer: &ArrayBuffer<'env>,
		element_type: TypedArrayType,
		offset: usize,
		length: usize,
	) -> Result<'static, TypedArray<'env>> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_create_typedarray(env.raw(), element_type as _, length, arraybuffer.inner, offset, &mut ret) }
			.result("napi_create_typedarray")?;
		Ok(TypedArray { env, inner: ret })
	}

	/// Creates a TypedArray from a raw napi_value
	pub fn with_raw<'env>(env: &'env Env, inner: sys::napi_value) -> TypedArray<'env> {
		TypedArray { env, inner }
	}

	/// Converts TypedArray to a generic Value
	pub fn v(&'_ self) -> Value<'val> {
		Value::new(self.env, self.inner)
	}

	/// Gets detailed information about the TypedArray
	pub fn info(&'_ self) -> Result<'static, (TypedArrayType, ArrayBuffer<'val>, &'val mut [u8], usize)> {
		let mut element_type = 0;
		let mut byte_offset = 0;
		let mut length = 0;
		let mut data = core::ptr::null_mut();
		let mut arraybuffer = core::ptr::null_mut();
		unsafe {
			sys::napi_get_typedarray_info(
				self.env.raw(),
				self.inner,
				&mut element_type,
				&mut length,
				&mut data,
				&mut arraybuffer,
				&mut byte_offset,
			)
		}
		.result("napi_get_typedarray_info")?;
		Ok((
			element_type.try_into()?,
			ArrayBuffer::with_raw(self.env, arraybuffer),
			unsafe { core::slice::from_raw_parts_mut(data as *mut u8, length) },
			byte_offset,
		))
	}
}

/// Enum representing different TypedArray types
#[repr(u32)]
pub enum TypedArrayType {
	Int8Array = sys::napi_int8_array,
	Uint8Array = sys::napi_uint8_array,
	Uint8ClampedArray = sys::napi_uint8_clamped_array,
	Int16Array = sys::napi_int16_array,
	Uint16Array = sys::napi_uint16_array,
	Int32Array = sys::napi_int32_array,
	Uint32Array = sys::napi_uint32_array,
	Float32Array = sys::napi_float32_array,
	Float64Array = sys::napi_float64_array,
}
impl TypedArrayType {
	pub fn data_size(self) -> usize {
		match self {
			Self::Int8Array => 1,
			Self::Uint8Array => 1,
			Self::Uint8ClampedArray => 1,
			Self::Int16Array => 2,
			Self::Uint16Array => 2,
			Self::Int32Array => 4,
			Self::Uint32Array => 4,
			Self::Float32Array => 4,
			Self::Float64Array => 8,
		}
	}
}
impl TryFrom<u32> for TypedArrayType {
	type Error = Status<'static>;
	fn try_from(value: u32) -> core::result::Result<Self, Self::Error> {
		Ok(match value {
			sys::napi_int8_array => Self::Int8Array,
			sys::napi_uint8_array => Self::Uint8Array,
			sys::napi_uint8_clamped_array => Self::Uint8ClampedArray,
			sys::napi_int16_array => Self::Int16Array,
			sys::napi_uint16_array => Self::Uint16Array,
			sys::napi_int32_array => Self::Int32Array,
			sys::napi_uint32_array => Self::Uint32Array,
			sys::napi_float32_array => Self::Float32Array,
			sys::napi_float64_array => Self::Float64Array,
			_ => return Err(Status::invalid_arg("invalid TypedArrayType")),
		})
	}
}

impl<'val> FromNapi<'val> for TypedArray<'val> {
	/// Converts from Value to TypedArray
	fn from_napi(v: Value<'val>) -> Result<'static, Self> {
		if v.is_typedarray()? {
			Ok(TypedArray::with_raw(v.env, v.inner))
		} else {
			Err(Status::invalid_arg("value is not a typedarray"))
		}
	}
}

impl<'val> IntoNapi for TypedArray<'val> {
	/// Converts from TypedArray to Value
	fn into_napi<'env>(self, env: &'env Env) -> std::result::Result<value::Value<'env>, status::Status<'static>> {
		if self.env.raw() != env.raw() {
			return Err(Status::invalid_arg("self.env not eq arg env"));
		}
		Ok(Value::new(env, self.inner))
	}
}
